//
//	File: OyTTFFont.cpp
//

#include "OyTTFFont.h"

#include "freetype\ttunpat.h"
#include "freetype\ftoutln.h"
#include <d3dx9.h>
#include "..\..\OyGeneral\OyGeneral.h"

Oy_TTF_Font::Oy_TTF_Font(FT_Library ftlib, LPDIRECT3DDEVICE9 pDevice, OyDirect3D9 *pOyD3D9)
: m_FTLib(ftlib), 
m_FontCharCache(INIT_FONTCACHE_ARRAY_NUM),
m_CharCacheTexture(1),
m_FontVB(1),
m_LastCacheIndex(0),
m_FirstCacheIndex(0),
m_pDevice(pDevice),
m_pOyD3D9(pOyD3D9),
m_Face(0),
m_CharWidth(20),
m_CharHeight(20),
m_SpaceAdvance(0),
m_MaxCharInRow(0),
m_bHasKerning(false),
m_fXScale(1.0f),
m_fYScale(1.0f),
m_fXHotSpot(0.5f),
m_fYHotSpot(0.5f),
m_bBold(false),
m_bItalic(false),
m_FontShadowColor(0xFF202020)
{
	memset( m_CharCacheMap, 0xFFFF, 65536*sizeof(USHORT) );


	for( int i=0; i<m_FontCharCache.Size(); i++ )
	{
		m_FontCharCache[i].iPrev = i-1;
		m_FontCharCache[i].iNext = i+1;
	}


	m_matItalic.xx = 1 << 16;
	m_matItalic.xy = 0x5800;
	m_matItalic.yx = 0;
	m_matItalic.yy = 1 << 16;
}

Oy_TTF_Font::~Oy_TTF_Font(void)
{
	UnloadFont();
}

bool Oy_TTF_Font::LoadTTFFontFromDisk(	const char* strname, const char* strpath, 
										int iSize, bool bBold, bool bItalic )
{
	UnloadFont();

	//create new face
	if( FT_New_Face(m_FTLib, strpath, 0, &m_Face) != 0 )
		return false;

	if( !m_Face->charmap || !FT_IS_SCALABLE(m_Face) )
	{
		FT_Done_Face(m_Face);
		return false;
	}

	strcpy_s(m_strFontName, strname);
	strcpy_s(m_strFontFilePath, strpath);

	if( FT_HAS_KERNING(m_Face) )
		m_bHasKerning = true;

	m_CharWidth	= iSize;
	m_CharHeight = iSize;
	m_MaxCharInRow = (USHORT)(mOyFontTextureSize / iSize);

	if( FT_Set_Pixel_Sizes(m_Face, m_CharWidth, m_CharHeight) != 0 )
		return false;

    m_fFaceAscender = m_Face->ascender * m_Face->size->metrics.y_scale * float(1.0/64.0) * (1.0f/65536.0f);

	m_bBold = bBold;
	m_bItalic = bItalic;

	m_SpaceAdvance = _GetSpaceAdvance();
	
	return true;
}

void Oy_TTF_Font::ClearAllCache(void)
{
	for( int i=0; i<m_FontCharCache.Size(); i++ )
	{
		m_FontCharCache[i].charCode = 0;

		m_FontCharCache[i].iPrev = i-1;
		m_FontCharCache[i].iNext = i+1;
	}

	memset( m_CharCacheMap, 0xFFFF, 65536 );

}

void Oy_TTF_Font::UnloadFont(void)
{
	ClearAllCache();

	if(m_Face)
	{
		FT_Done_Face(m_Face);
		m_Face = 0;
	}

	for( int i=0; i<m_CharCacheTexture.Size(); i++ )
	{
		if(m_CharCacheTexture[i].pData)
		{
			((LPDIRECT3DTEXTURE9)(m_CharCacheTexture[i].pData))->Release();
			m_CharCacheTexture[i].pData = NULL;
		}
		if(m_CharCacheTexture[i].chName)
		{
			delete [] m_CharCacheTexture[i].chName;
			m_CharCacheTexture[i].chName = NULL;
		}
	}
	for( int i=0; i<m_FontVB.Size(); i++ )
	{
		if( m_FontVB[i] )
			delete m_FontVB[i];
		m_FontVB[i] = NULL;
	}
}

void Oy_TTF_Font::OnDeviceLost(void)
{
	for( int i=0; i<m_FontVB.Size(); i++ )
	{
		if( m_FontVB[i] )
			m_FontVB[i]->OnDeviceLost();
	}
}

void Oy_TTF_Font::OnDeviceReset(void)
{
	for( int i=0; i<m_FontVB.Size(); i++ )
	{
		if( m_FontVB[i] )
			m_FontVB[i]->OnDeviceReset();
	}
}

bool Oy_TTF_Font::IsCharCached(TCHAR charCode)
{
	return m_CharCacheMap[charCode] != 0xFFFF;
}


bool Oy_TTF_Font::DrawTextInPos(const TCHAR* sText, float x, float y, DWORD FontColor, 
	DWORD flag, float fFontSize, bool bKerning, bool bUnderl)
{
	bool Ret = true;
	float penX = x, penY = y;
	UINT32 previousGlyph = 0;
	FT_Vector Delta;
	CharCacheNode* pNode = NULL;
	float scale = fFontSize / m_CharWidth;


	int StrLength = _tcslen(sText);
	float original_Pen_x = x;

	for(int i = 0; i < StrLength; ++i)
	{
		//Special cases
		switch(sText[i])
		{
		case L' ':
			penX += m_SpaceAdvance*scale;	
			previousGlyph = 0; 
			continue;
		case L'\t': 
			penX += m_SpaceAdvance * nTabSize * scale;	
			previousGlyph = 0; 
			continue;
		case L'\n':	
			// Draw underline
			if( bUnderl )
			{
				_DoDrawBorder(original_Pen_x, penX, penY + m_CharHeight*scale, FontColor, fFontSize);
			}
			penY += m_CharHeight * scale; 
			penX = x;	
			original_Pen_x = x;
			previousGlyph = 0; 
			continue;
		}

		_BuildCharCache(sText[i]);

		pNode = &m_FontCharCache[m_CharCacheMap[sText[i]]];
		if( !pNode )
		{
			Ret = false;
			previousGlyph = 0;
			continue;
		}

		// glyph index in the font face
		UINT32 charGlyphIndex = FT_Get_Char_Index(m_Face, sText[i]);

		//Kerning
		if( previousGlyph != 0 && m_bHasKerning && bKerning )
		{			
			FT_Get_Kerning(m_Face, previousGlyph, charGlyphIndex, FT_KERNING_DEFAULT, &Delta);
			penX += Delta.x >> 6;
			penY += Delta.y >> 6;
		}

		if( !_RenderChar(sText[i], penX, penY, FontColor, flag, fFontSize, bKerning, bUnderl) )
			Ret = false;

		penX += pNode->charAdvance * scale;
		previousGlyph = charGlyphIndex;
	}

	// Draw underline
	if( bUnderl && ((penX - original_Pen_x) > 0.1f) )
	{
		_DoDrawBorder(original_Pen_x, penX, penY + m_CharHeight*scale, FontColor, fFontSize);
	}
	return Ret;
}

bool Oy_TTF_Font::DwawTextShadowInPos( const TCHAR* sText, float x, float y, DWORD FontColor, DWORD flag,
	float fFontSize, bool bKerning, bool bUnderl )
{
	float scale = fFontSize / m_CharWidth;
	return DrawTextInPos( sText, x+m_CharWidth*scale*0.05f, y+m_CharHeight*scale*0.05f, m_FontShadowColor, flag, fFontSize, bKerning, bUnderl );
}

void Oy_TTF_Font::PreCacheChar(const TCHAR* sText)
{
	int StrLength = _tcslen(sText);
	for(int i = 0; i < StrLength; ++i)
	{
		if( (sText[i] != L' ') && (sText[i] != L'\t') && (sText[i] != L'\n') )
			_BuildCharCache(sText[i]);
	}
}

















bool Oy_TTF_Font::_BuildCharCache(TCHAR charCode)
{
	static USHORT TexX = 0;
	static USHORT TexY = 0;

	if( IsCharCached(charCode) )
	{
		USHORT CacheIdx = m_CharCacheMap[charCode];

		// if cache index is Head, do none
		if( m_FirstCacheIndex == CacheIdx )
			return true;

		// move found cache index to Head
		m_FontCharCache[m_FontCharCache[CacheIdx].iPrev].iNext = m_FontCharCache[CacheIdx].iNext;
				

		m_FontCharCache[CacheIdx].iPrev = 0xFFFF;
		m_FontCharCache[CacheIdx].iNext = m_FirstCacheIndex;
		m_FontCharCache[m_FirstCacheIndex].iPrev = CacheIdx;

		m_FirstCacheIndex = CacheIdx;

		return true;
	}

	CharCacheNode *pNode = &m_FontCharCache[m_LastCacheIndex];
	//////////
	//init the struct pointers
	pNode->charCode = 0;
	//////////
	
	UINT32 charGlyphIndex = FT_Get_Char_Index(m_Face, charCode);
	if( charGlyphIndex == 0 )
		return false;

	FT_Load_Char(m_Face, charCode, FT_LOAD_DEFAULT/* | FT_LOAD_NO_BITMAP*/);

	if( m_bBold )
	{
		int strength = 1 << 6;
		FT_Outline_Embolden(&m_Face->glyph->outline, strength);
	}

	if(m_bItalic)
	{
		// set transformation 
		FT_Outline_Transform(&m_Face->glyph->outline, &m_matItalic);
	}


	if( FT_Render_Glyph(m_Face->glyph, FT_RENDER_MODE_NORMAL) )
	{
		return false;
	}

	// Cache NOT full, texture has space for new font
	if( m_FontCharCache.Count() < INIT_FONTCACHE_ARRAY_NUM )
	{
		// One texture is full?
		if( (m_FontCharCache.Count() % (m_MaxCharInRow*m_MaxCharInRow) == 0) &&
			m_CharCacheTexture.Count() <= m_FontCharCache.Count()/(m_MaxCharInRow*m_MaxCharInRow) )
		{
			OyTexture FontTexture;
			FontTexture.pData = NULL;
			FontTexture.chName = NULL;
			
			m_pDevice->CreateTexture(mOyFontTextureSize, mOyFontTextureSize, 1, 0, 
				D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, (IDirect3DTexture9**)&FontTexture.pData, NULL );
			if( !m_CharCacheTexture.Push(FontTexture) )
			{
				m_CharCacheTexture.Resize(m_CharCacheTexture.Size() + 1);
				m_CharCacheTexture.Push(FontTexture);
			}
			OyD3D9_FontBuffer *pFontVB = new OyD3D9_FontBuffer((LPDIRECT3DTEXTURE9)FontTexture.pData, m_pDevice, m_pOyD3D9);
			if( !m_FontVB.Push(pFontVB) )
			{
				m_FontVB.Resize(m_FontVB.Size() + 1);
				m_FontVB.Push(pFontVB);
			}
		}
		// Add to cache
		m_FontCharCache.Push(*pNode);
	}

	// Calculate texture update Offset_X and Offset_Y

	USHORT TexOffset = m_LastCacheIndex - USHORT(m_LastCacheIndex/(m_MaxCharInRow*m_MaxCharInRow)) * (m_MaxCharInRow*m_MaxCharInRow);
	USHORT TexOffset_Y = (TexOffset / m_MaxCharInRow) * m_CharHeight;
	USHORT TexOffset_X = (TexOffset - USHORT(TexOffset / m_MaxCharInRow) * m_MaxCharInRow) * m_CharWidth;
		

	if( !_RenderGlyph(&m_Face->glyph->bitmap, &m_CharCacheTexture[m_LastCacheIndex/(m_MaxCharInRow*m_MaxCharInRow)], TexOffset_X, TexOffset_Y) )
	{
		m_FontCharCache.Pop();
		return false;
	}



	pNode->charOffsetX = m_Face->glyph->bitmap_left;
	pNode->charOffsetY = m_Face->glyph->bitmap_top;
	pNode->charAdvance = m_Face->glyph->advance.x / 64;
	pNode->charCode = charCode;


	USHORT BackupIdx = m_LastCacheIndex;

	if( m_FontCharCache.Count() < INIT_FONTCACHE_ARRAY_NUM )
	{
		m_LastCacheIndex++;
	}
	else
	{
		// Move Last cache index to Head
		m_LastCacheIndex = m_FontCharCache[m_LastCacheIndex].iPrev;

		m_FontCharCache[m_FontCharCache[BackupIdx].iPrev].iNext = 0xFFFF;
		m_FontCharCache[BackupIdx].iPrev = 0xFFFF;
		m_FontCharCache[BackupIdx].iNext = m_FirstCacheIndex;
		m_FontCharCache[m_FirstCacheIndex].iPrev = BackupIdx;
		m_FirstCacheIndex = BackupIdx;
	}


	m_CharCacheMap[charCode] = BackupIdx;

	
	return true;
}



bool Oy_TTF_Font::_RenderGlyph(FT_Bitmap* ftBMP, OyTexture* pFontTexture, USHORT x, USHORT y)
{
	UINT32 glyphWidth = ftBMP->width;
	UINT32 glyphHeight = ftBMP->rows;

	if( glyphWidth == 0 || glyphHeight == 0 || pFontTexture == NULL )
		return false;



	D3DLOCKED_RECT LockedRect;
	((LPDIRECT3DTEXTURE9)pFontTexture->pData)->LockRect(0, &LockedRect, NULL, 0);

	unsigned char* pSrc = 0;
	unsigned char  alpha = 0;
	byte* pDst = (BYTE*)( (BYTE *)LockedRect.pBits + y * LockedRect.Pitch + x*2 );

	for(UINT32 j = 0 ; j < glyphHeight; j++)
	{
		for(UINT32 i = 0 ; i < glyphWidth; i++)
		{
			switch(ftBMP->pixel_mode)
			{
				case FT_PIXEL_MODE_GRAY:
					pSrc = ftBMP->buffer + j * ftBMP->width + i;
					// ARGB
					alpha = (*(pSrc)) >> 4;
					*(WORD*)(&pDst[j*LockedRect.Pitch + i*2]) = 
						WORD(alpha << 12) |	WORD(alpha << 8) | WORD(alpha << 4) | WORD(alpha);
					break;
				case FT_PIXEL_MODE_MONO:
					if((ftBMP->buffer[j * ftBMP->pitch + i/8] & (0x80 >> (i & 7))))
					{
						*(WORD*)(&pDst[j*LockedRect.Pitch + i*2]) = 0xFFFF;
					}
					else
					{
						*(WORD*)(&pDst[j*LockedRect.Pitch + i*2]) = 0;
					}
					break;
				default:
					break;
			}
		}
	}

	((LPDIRECT3DTEXTURE9)pFontTexture->pData)->UnlockRect(0);
	
	return true;
}


int Oy_TTF_Font::_GetSpaceAdvance()
{
	//We use the advance value of 'A' as the space value
	_BuildCharCache(L'A');
	CharCacheNode* pNode = &m_FontCharCache[m_CharCacheMap[L'A']];

/*	
	D3DXSaveTextureToFile( L"test.dds", D3DXIFF_DDS, 
		(LPDIRECT3DBASETEXTURE9)(m_CharCacheTexture[ m_CharCacheMap[L'A'] / (m_MaxCharInRow*m_MaxCharInRow) ].pData),
		NULL );
*/

	if(pNode->charCode)
		return pNode->charAdvance;
	else// no 'A' in this face
		return m_CharWidth / 2;
}

void Oy_TTF_Font::_DoDrawBorder(float fX_s, float fX_e, float fY, DWORD color, float fFontSize)
{
	return;
}

bool Oy_TTF_Font::_RenderChar( TCHAR charCode, float x, float y ,DWORD FontColor, 
	DWORD flag, float fFontSize, bool bKerning, bool bUnderl )
{
	
	float scale = fFontSize / m_CharWidth;
	float txscale = 1.0f / mOyFontTextureSize;
	float tyscale = 1.0f / mOyFontTextureSize;


	CharCacheNode* pNode = &m_FontCharCache[m_CharCacheMap[charCode]];
	if( !pNode )
		return false;

	OyTexture *pTex = &m_CharCacheTexture[ m_CharCacheMap[charCode] / (m_MaxCharInRow*m_MaxCharInRow) ];
	
	USHORT TexOffset = m_CharCacheMap[charCode] - USHORT(m_CharCacheMap[charCode]/(m_MaxCharInRow*m_MaxCharInRow)) * (m_MaxCharInRow*m_MaxCharInRow);
	USHORT V = (TexOffset / m_MaxCharInRow) * m_CharHeight;
	USHORT U = (TexOffset - USHORT(TexOffset / m_MaxCharInRow) * m_MaxCharInRow) * m_CharWidth;


	float tx = U * txscale - 0.5f*txscale;
	float ty = V * tyscale - 0.5f*tyscale;
	float tw = m_CharWidth * txscale;
	float th = m_CharHeight * tyscale;
	float w = m_CharWidth * scale;
	float h = m_CharHeight * scale;
	float offsetx = pNode->charOffsetX * scale;	
	float offsety = pNode->charOffsetY * scale;	
	float advancedx = pNode->charAdvance * scale;	

	float cx = x + offsetx;
	float cy = y + (m_fFaceAscender - pNode->charOffsetY) * scale;


	OY_FONT_VERTEX_TYPE vb[4];
	vb[0].x = cx;
	vb[0].y = cy;
	vb[0].z = 0;
	vb[0].w = 1;
	vb[0].VertexColor = FontColor;
	vb[0].tu = tx; vb[0].tv = ty;
	vb[1].x = cx;
	vb[1].y = cy + h;
	vb[1].z = 0;
	vb[1].w = 1;
	vb[1].VertexColor = FontColor;
	vb[1].tu = tx; vb[1].tv = ty + th;
	vb[2].x = cx + w;
	vb[2].y = cy + h;
	vb[2].z = 0;
	vb[2].w = 1;
	vb[2].VertexColor = FontColor;
	vb[2].tu = tx + tw; vb[2].tv = ty + th;
	vb[3].x = cx + w;
	vb[3].y = cy;
	vb[3].z = 0;
	vb[3].w = 1;
	vb[3].VertexColor = FontColor;
	vb[3].tu = tx + tw; vb[3].tv = ty;

	OyD3D9_FontBuffer *pFontBuffer = m_FontVB[ m_CharCacheMap[charCode] / (m_MaxCharInRow*m_MaxCharInRow) ];
	if( pFontBuffer->IsBufferFull() )
	{
		if( pFontBuffer->Flush() != OY_OK )
		{
			OyLog_Write_L1(OyXMLLog::Error, OyXMLLog::Engine, L"error: Font Render Error!");
			return false;
		}
	}

	pFontBuffer->PushVertex(vb);
	return true;
}

HRESULT Oy_TTF_Font::FlushAllVB()
{
	HRESULT hr = OY_OK;

	for( int i=0; i<m_FontVB.Size(); i++ )
		if( m_FontVB[i]->Flush() != OY_OK )
			hr = OY_FAIL;

	return hr;
}

